<?php

/**
 * Helper class to save downloaded content with HTTP_Request
 *
 * PHP version 5
 *
 * LICENSE:
 *
 * Copyright (c) 2008, Philippe Jausions / 11abacus
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   - Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   - Neither the name of the 11abacus nor the names of its contributors may
 *     be used to endorse or promote products derived from this software
 *     without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @category  HTTP
 * @package   HTTP_Request_SaveAs
 * @author    Philippe Jausions <jausions@php.net>
 * @copyright 2008 by Philippe Jausions / 11abacus
 * @license   New BSD
 */

/**
 * Package dependencies
 */
require_once 'HTTP/Request/Listener.php';
require_once 'HTTP/Request/SaveAs/Exception.php';
require_once 'System/Folders.php';

/**
 * Helper class to save downloaded content with HTTP_Request
 *
 * @category  HTTP
 * @package   HTTP_Request_SaveAs
 * @author    Philippe Jausions <jausions@php.net>
 * @copyright 2008 by Philippe Jausions / 11abacus
 * @license   New BSD
 */
class HTTP_Request_SaveAs extends HTTP_Request_Listener
{
    /**
     * File pointer
     *
     * @var resource
     */
    protected $fp;

    /**
     * File path
     *
     * @var string
     */
    protected $file;

    /**
     * HTTP_Request the instance is attached to
     *
     * @var HTTP_Request
     */
    protected $httpRequest;

    /**
     * Whether to save the body of the response
     *
     * @var boolean
     */
    protected $saveBody;

    /**
     * Options
     *
     * @var array
     */
    protected $options = array(
        'saveHeaders' => false,
        'saveBody' => true,
    );

    /**
     * Class constructor
     *
     * @param string $file    path of file to download to
     * @param array  $options array of options, available indexes:
     * <ul>
     *  <li>saveHeaders (boolean) Whether to save the HTTP headers in the
     *      file</li>
     *  <li>saveBody (boolean) Whether to save the body of the response in the
     *      file</li>
     *  <li>httpStatus (array|integer|string) save the content only when the
     *      HTTP response status is in the list provided. Pass * for all
     *      status. (default is to only save for HTTP Status 200)</li>
     * </ul>
     *
     * @access public
     */
    public function __construct($file = null, array $options = array())
    {
        $this->HTTP_Request_Listener();
        $this->file = $file;
        if (!array_key_exists('httpStatus', $options)) {
            $options['httpStatus'] = array(200);
        } else {
            settype($options['httpStatus'], 'array');
        }
        $this->options = array_merge($this->options, $options);
    }

    /**
     * Property setter
     *
     * @param string $var   property name
     * @param mixed  $value property value
     *
     * @return void
     */
    public function __set($var, $value)
    {
        switch ($var) {
        case 'file':
            $this->file = $value;
            $this->close();
            break;
        default:
            throw new HTTP_Request_SaveAs_Exception('Unknown property "'.$var.'"');
        }
    }

    /**
     * Property getter
     *
     * @param string $var property name
     *
     * @return mixed
     */
    public function __get($var)
    {
        switch ($var) {
        case 'file':
            return $this->file;
            break;
        }
        throw new HTTP_Request_SaveAs_Exception('Unknown property "'.$var.'"');
    }

    /**
     * Initializes the downloader
     *
     * This method is automatically called when the downloader starts
     * receiving data. You can also call it before the HTTP request is sent
     * to ensure you will be able to save the file.
     *
     * @return void
     */
    public function init()
    {
        if (!$this->options['saveBody'] && !$this->options['saveHeaders']) {
            return;
        }
        if (is_resource($this->fp)) {
            return;
        }

        if (!isset($this->file) || strlen(trim($this->file)) == 0) {
            static $tempDir;
            if (!isset($tempDir)) {
                $sf = new System_Folders();

                $tempDir = $sf->getTemp();
            }
            $this->file = tempnam($tempDir, 'HTTP_Request__');
        }

        if (!($this->fp = fopen($this->file, 'wb'))) {
            throw new HTTP_Request_SaveAs_Exception('Could not open destination file '.$this->file);
        }
    }

    /**
     * Closes the file pointer
     *
     * @return void
     */
    protected function close()
    {
        if (is_resource($this->fp)) {
            fclose($this->fp);
            $this->fp = null;
        }
        $this->httpRequest = null;
        $this->saveBody    = $this->options['saveBody'];
    }

    /**
     * Receives and saves data to file
     *
     * @param object $subject an object the listener is attached to
     * @param string $event   event name
     * @param mixed  $data    additional data
     *
     * @return void
     */
    public function update($subject, $event, $data = null)
    {
        switch ($event) {
        case 'sentRequest':
        case 'gotHeaders':
        case 'tick':
        case 'gotBody':
            $this->$event($subject, $data);
            break;
        default:
            throw new HTTP_Request_SaveAs_Exception('Unhandled HTTP_Request event "'.$event.'"');
        }
    }

    /**
     * Handles the "sentRequest" event
     *
     * @param object $subject an object the listener is attached to
     * @param mixed  $data    additional data
     *
     * @return void
     */
    protected function sentRequest($subject, $data)
    {
        if (!($subject instanceof HTTP_Request)) {
            throw new HTTP_Request_SaveAs_Exception('Unexpected subject class');
        }
        $this->httpRequest = $subject;
        $this->saveBody    = $this->options['saveBody'];
    }

    /**
     * Handles the "gotHeaders" event
     *
     * The first line of the HTTP Header (HTTP/1.x xxx xxx...) may not be
     * exactly what was recieved.
     *
     * @param object $subject an object the listener is attached to
     * @param mixed  $data    additional data
     *
     * @return void
     */
    protected function gotHeaders($subject, $data)
    {
        $this->init();
        $httpCode = $this->httpRequest->getResponseCode();

        if ($this->options['saveHeaders']) {
            $s = 'HTTP/1.? '.$httpCode."\n";
            foreach ($data as $name => $value) {
                $s .= $name.': '.$value."\n";
            }
            $s .= "\n";
            if (fwrite($this->fp, $s) === false) {
                throw new HTTP_Request_SaveAs_Exception('Could not save HTTP headers');
            }
        }

        if ($this->saveBody
            && !in_array($httpCode, $this->options['httpStatus'])
            && !in_array('*', $this->options['httpStatus'])) {
            $this->saveBody = false;
        }
    }

    /**
     * Handles the "tick" event
     *
     * @param object $subject an object the listener is attached to
     * @param mixed  $data    additional data
     *
     * @return void
     */
    protected function tick($subject, $data)
    {
        if ($this->saveBody && (fwrite($this->fp, $data) === false)) {
            throw new HTTP_Request_SaveAs_Exception('Could not save data');
        }
    }

    /**
     * Handles the "gotBody" event
     *
     * @param object $subject an object the listener is attached to
     * @param mixed  $data    additional data
     *
     * @return void
     */
    protected function gotBody($subject, $data)
    {
        $this->close();
    }

    /**
     * Class destructor
     */
    public function __destruct()
    {
        $this->close();
    }
}

?>